<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title> Infinite Loop Scrolling (3D) </title>

    <style>
        *,
        *::after,
        *::before {
            box-sizing: border-box;
        }

        :root {
            font-size: 15px;
        }

        body {
            margin: 0;
            --color-text: #fff;
            --color-bg: #000;
            --color-link: rgba(255, 255, 255, 0.7);
            --color-link-hover: #fff;
            color: var(--color-text);
            background-color: var(--color-bg);
            font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif;
            font-weight: 500;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;

            min-height: 100vh;
        }

        /* Page Loader */
        .js .loading::before,
        .js .loading::after {
            content: '';
            position: fixed;
            z-index: 1000;
        }

        .js .loading::before {
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: var(--color-bg);
        }

        .js .loading::after {
            top: 50%;
            left: 50%;
            width: 60px;
            height: 60px;
            margin: -30px 0 0 -30px;
            border-radius: 50%;
            opacity: 0.4;
            background: var(--color-link);
            animation: loaderAnim 0.7s linear infinite alternate forwards;

        }

        @keyframes loaderAnim {
            to {
                opacity: 1;
                transform: scale3d(0.5, 0.5, 1);
            }
        }

        a {
            text-decoration: none;
            color: var(--color-link);
            outline: none;
        }

        a:hover {
            color: var(--color-link-hover);
            outline: none;
        }

        /* Better focus styles from https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible */
        a:focus {
            /* Provide a fallback style for browsers
             that don't support :focus-visible */
            outline: none;
            background: lightgrey;
        }

        a:focus:not(:focus-visible) {
            /* Remove the focus indicator on mouse-focus for browsers
             that do support :focus-visible */
            background: transparent;
        }

        a:focus-visible {
            /* Draw a very noticeable focus style for
             keyboard-focus on browsers that do support
             :focus-visible */
            outline: 2px solid #fff;
            background: transparent;
        }

        .unbutton {
            background: none;
            border: 0;
            padding: 0;
            margin: 0;
            font: inherit;
            cursor: pointer;
        }

        .unbutton:focus {
            outline: none;
        }

        .frame {
            z-index: 100;
            pointer-events: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: auto;
            padding: 1.5rem 2rem;
            display: grid;
            grid-template-columns: auto 1fr auto;
            grid-template-areas: 'title links sponsor' 'credits credits credits';
            grid-column-gap: 3vw;
            grid-row-gap: 1rem;
            justify-content: space-between;
            align-content: space-between;
            text-align: left;
        }

        .frame a {
            pointer-events: auto;
        }

        .frame__title {
            margin: 0;
            font-size: 1rem;
            font-weight: 500;
            grid-area: title;
        }

        .frame__links {
            grid-area: links;
        }

        .frame__links a:not(:last-child) {
            margin-right: 1rem;
        }

        .frame__credits {
            grid-area: credits;
        }

        .grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 5vh;
        }

        .grid__item {
            height: 100vh;
            background-size: cover;
            background-position: 50% 50%;
        }

        @media screen and (min-width: 53em) {
            .frame {
                height: 100vh;
                align-items: space-between;
            }
        }

    </style>
</head>

<body class="loading">
<main>
    <div class="frame">
        <h1 class="frame__title">Sketch 029: Infinite Loop (3D) </h1>

        <span class="frame__credits">Photography by <a href="https://www.instagram.com/Igordream/">Igor Rand</a></span>
    </div>
    <div class="grid">
        <div class="grid__item" style="background-image:url(img/1.jpg);"></div>
        <div class="grid__item" style="background-image:url(img/2.jpg);"></div>
        <div class="grid__item" style="background-image:url(img/3.jpg);"></div>
        <div class="grid__item" style="background-image:url(img/4.jpg);"></div>
        <div class="grid__item" style="background-image:url(img/5.jpg);"></div>
        <div class="grid__item" style="background-image:url(img/6.jpg);"></div>
    </div>
</main>

<script src="https://cdn.jsdelivr.net/gh/studio-freight/lenis@0.2.28/bundled/lenis.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/imagesloaded@5/imagesloaded.pkgd.min.js"></script>
<script>
    gsap.registerPlugin(ScrollTrigger);

    // repeat first three items by cloning them and appending them to the .grid
    const repeatItems = (parentEl, total = 0) => {
        const items = [...parentEl.children];
        for (let i = 0; i <= total - 1; ++i) {
            var cln = items[i].cloneNode(true);
            parentEl.appendChild(cln);
        }
    };

    const lenis = new Lenis({
        smooth: true,
        infinite: true
    });

    lenis.on('scroll', () => {
        ScrollTrigger.update()
    })

    function raf(time) {
        lenis.raf(time);
        requestAnimationFrame(raf);
    }

    imagesLoaded(document.querySelectorAll('.grid__item'), {background: true}, () => {
        document.body.classList.remove('loading');

        repeatItems(document.querySelector('.grid'), 3);

        // first three items
        [...document.querySelectorAll('.grid__item:nth-child(-n+3)')].forEach(el => {

            gsap.set(el.parentNode, {perspective: 1000});
            gsap.set(el, {transformOrigin: `0% 100%`})
            gsap.to(el, {
                ease: 'none',
                startAt: {scale: 1, rotationY: 0, rotationX: 0, filter: 'brightness(1)'},
                scale: 0,
                rotationY: 70,
                rotationX: 10,
                filter: 'brightness(3)',
                scrollTrigger: {
                    trigger: el,
                    start: "center center",
                    end: "bottom top",
                    scrub: true,
                    fastScrollEnd: true,
                    onLeave: () => {
                        gsap.set(el, {scale: 1, rotationY: 0, rotationX: 0, filter: 'brightness(1)'})
                    },
                }
            });

        });

        // last three items
        [...document.querySelectorAll('.grid__item:nth-last-child(-n+3)')].forEach(el => {

            gsap.set(el.parentNode, {perspective: 1000});
            gsap.set(el, {transformOrigin: `100% 0%`, scale: 0})
            gsap.to(el, {
                ease: 'none',
                startAt: {scale: 0, rotationY: 70, rotationX: 10, filter: 'brightness(3)'},
                scale: 1,
                rotationY: 0,
                rotationX: 0,
                filter: 'brightness(1)',
                scrollTrigger: {
                    trigger: el,
                    start: "top bottom",
                    end: "bottom top",
                    scrub: true,
                    fastScrollEnd: true,
                    onLeaveBack: () => {
                        gsap.set(el, {scale: 1, rotationY: 0, rotationX: 0, filter: 'brightness(1)'})
                    }
                }
            });

        });

        // in between
        let ft;
        let st;
        [...document.querySelectorAll('.grid__item:nth-child(4), .grid__item:nth-child(5), .grid__item:nth-child(6)')].forEach(el => {

            gsap.set(el.parentNode, {perspective: 1000});

            ft = gsap.timeline()
                .to(el, {
                    scrollTrigger: {
                        trigger: el,
                        start: 'top bottom',
                        end: 'center center',
                        scrub: true,
                        onEnter: () => gsap.set(el, {transformOrigin: `100% 0%`}),
                        onEnterBack: () => gsap.set(el, {transformOrigin: `100% 0%`}),
                        onLeave: () => gsap.set(el, {transformOrigin: `0% 100%`}),
                        onLeaveBack: () => gsap.set(el, {transformOrigin: `0% 100%`}),
                    },
                    onStart: () => {
                        if (st) st.kill()
                    },
                    startAt: {scale: 0, rotationY: -70, rotationX: 10, filter: 'brightness(3)'},
                    scale: 1,
                    rotationY: 0,
                    rotationX: 0,
                    filter: 'brightness(1)',
                    ease: 'none'
                });

            st = gsap.timeline()
                .to(el, {
                    scrollTrigger: {
                        trigger: el,
                        start: 'center center',
                        end: 'bottom top',
                        scrub: true,
                        onEnter: () => gsap.set(el, {transformOrigin: `0% 100%`}),
                        onEnterBack: () => gsap.set(el, {transformOrigin: `0% 100%`}),
                        onLeave: () => gsap.set(el, {transformOrigin: `100% 0%`}),
                        onLeaveBack: () => gsap.set(el, {transformOrigin: `100% 0%`}),
                    },
                    onStart: () => {
                        if (ft) ft.kill()
                    },
                    startAt: {scale: 1, rotationY: 0, rotationX: 0, filter: 'brightness(1)'},
                    scale: 0,
                    rotationY: 70,
                    rotationX: 10,
                    filter: 'brightness(3)',
                    ease: 'none'
                });

        });

        requestAnimationFrame(raf);

        const refresh = () => {
            ScrollTrigger.clearScrollMemory();
            window.history.scrollRestoration = 'manual';
            ScrollTrigger.refresh(true);
        }

        refresh();
        window.addEventListener('resize', refresh);
    });
</script>
</body>
</html>
